/************************************
 * Package: RGcpp					*
 * Authors: Rui Eduardo Gouveia Gil	*
 * Date: 2010						*
 ************************************/
#include "rgcpp/net/sockets/DatagramSocket.h"

namespace rgcpp
{

DatagramSocket::DatagramSocket() : releaseForced(false), ipAddress((rand()%(65535-1025))+1025)
{
	struct sockaddr_in addr;

	if((socketFD = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		Throw(SocketException(std::string("Error creating the datagram socket - ")+strerror(errno)));
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(ipAddress.getPort());
	addr.sin_addr.s_addr = INADDR_ANY;

	//Open the datagram socket
	if(::bind(socketFD, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
#ifdef _WIN32
		shutdown(socketFD,SD_BOTH);
		closesocket(socketFD);
#else
		shutdown(socketFD,SHUT_RDWR);
		close(socketFD);
#endif
		Throw(SocketException(std::string("Error creating the datagram socket - ")+strerror(errno)));
	}

	timeout.tv_usec = NO_TIMEOUT;
	timeout.tv_sec = NO_TIMEOUT;
}

DatagramSocket::DatagramSocket(const unsigned short port) : releaseForced(false), ipAddress(port)
{
	struct sockaddr_in addr;

	if((socketFD = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		Throw(SocketException(std::string("Error creating the datagram socket - ")+strerror(errno)));
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;

	//Open the datagram socket
	if(::bind(socketFD, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
#ifdef _WIN32
		shutdown(socketFD,SD_BOTH);
		closesocket(socketFD);
#else
		shutdown(socketFD,SHUT_RDWR);
		close(socketFD);
#endif
		Throw(SocketException(std::string("Error creating the datagram socket - ")+strerror(errno)));
	}

	timeout.tv_usec = NO_TIMEOUT;
	timeout.tv_sec = NO_TIMEOUT;
}

DatagramSocket::DatagramSocket(const IPAddress& ipAddr) : releaseForced(false), ipAddress(ipAddr)
{
	struct sockaddr_in addr;

	if((socketFD = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		Throw(SocketException(std::string("Error creating the datagram socket - ")+strerror(errno)));
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(ipAddress.getPort());
	addr.sin_addr.s_addr = htonl(ipAddress.getIP());

	//Open the datagram socket
	if(::bind(socketFD, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
#ifdef _WIN32
		shutdown(socketFD,SD_BOTH);
		closesocket(socketFD);
#else
		shutdown(socketFD,SHUT_RDWR);
		close(socketFD);
#endif
		Throw(SocketException(std::string("Error creating the datagram socket - ")+strerror(errno)));
	}

	timeout.tv_usec = NO_TIMEOUT;
	timeout.tv_sec = NO_TIMEOUT;
}

DatagramSocket::~DatagramSocket()
{
	boost::mutex::scoped_lock lockRead(mutRead);
	boost::mutex::scoped_lock lockWrite(mutWrite);

#ifdef _WIN32
	shutdown(socketFD,SD_BOTH);
	closesocket(socketFD);
#else
	shutdown(socketFD,SHUT_RDWR);
	close(socketFD);
#endif
}

void DatagramSocket::sendMsg(const char* buffer, const unsigned int size)
{
	boost::mutex::scoped_lock lock(mutWrite);

	struct sockaddr_in peerAddr;
	memset(&peerAddr, 0, sizeof(peerAddr));

	peerAddr.sin_family = AF_INET;
	peerAddr.sin_port = htons(peerAddress.getPort());
	peerAddr.sin_addr.s_addr = htonl(peerAddress.getIP());

#ifndef _WIN32
	int writeSize = sendto(socketFD,buffer,size,MSG_NOSIGNAL,(struct sockaddr *)&peerAddr, sizeof(peerAddr));
#else
	int writeSize = sendto(socketFD,buffer,size,0,(struct sockaddr *)&peerAddr, sizeof(peerAddr));
#endif
	if(writeSize<=0)
	{
		Throw(SocketException(std::string("Error sending msg through datagram socket - ")+strerror(errno)));
	}

#ifndef _WIN32
	fsync(socketFD);
#endif
}

void DatagramSocket::sendMsg(const char* buffer, const unsigned int size, const IPAddress& ipAddress)
{
	boost::mutex::scoped_lock lock(mutWrite);

	struct sockaddr_in peerAddr;
	memset(&peerAddr, 0, sizeof(peerAddr));

	peerAddr.sin_family = AF_INET;
	peerAddr.sin_port = htons(ipAddress.getPort());
	peerAddr.sin_addr.s_addr = htonl(ipAddress.getIP());

#ifndef _WIN32
	int writeSize = sendto(socketFD,buffer,size,MSG_NOSIGNAL,(struct sockaddr *)&peerAddr, sizeof(peerAddr));
#else
	int writeSize = sendto(socketFD,buffer,size,0,(struct sockaddr *)&peerAddr, sizeof(peerAddr));
#endif
	if(writeSize<=0)
	{
		Throw(SocketException(std::string("Error sending msg through datagram socket - ")+strerror(errno)));
	}

#ifndef _WIN32
	fsync(socketFD);
#endif
}

int DatagramSocket::receiveMsg(char* buffer, const unsigned int size)
{
	return this->receiveMsg(buffer,size,peerAddress);
}

int DatagramSocket::receiveMsg(char* buffer, const unsigned int size, IPAddress& ipAddress)
{
	boost::mutex::scoped_lock lock(mutRead);

	fd_set fdList;
	FD_ZERO(&fdList);
	FD_SET(socketFD, &fdList);

	int value;
	struct timeval timeout = this->timeout;
	do
	{
		if(!timeout.tv_usec && !timeout.tv_sec)
			value = select(socketFD+1,&fdList,NULL,NULL,NULL);
		else
			value = select(socketFD+1,&fdList,NULL,NULL,&timeout);
	}while(value == -1&& errno == EINTR);

	if(value == 0)
	{
		Throw(SocketException("Error reading msg from datagram socket - Timeout exception"));
	}
	else if(value == -1)
	{
		Throw(SocketException(std::string("Error reading msg from datagram socket - ")+strerror(errno)));
	}

	if(this->releaseForced)
		return 0;

	struct sockaddr_in peerAddr;
#ifdef _WIN32
	int len = sizeof(peerAddr);
#else
	socklen_t len = sizeof(peerAddr);
#endif

	int readSize = recvfrom(socketFD,buffer,size,0,(struct sockaddr *)&peerAddr, &len);

	ipAddress.setIP(ntohl(peerAddr.sin_addr.s_addr));
	ipAddress.setPort(ntohs(peerAddr.sin_port));
	peerAddress.setIP(ntohl(peerAddr.sin_addr.s_addr));
	peerAddress.setPort(ntohs(peerAddr.sin_port));

	if(readSize == 0)
	{
		Throw(SocketException("Error reading msg from datagram socket - Socket Disconnected"));
	}
	else if(readSize == -1)
	{
		Throw(SocketException(std::string("Error reading msg from datagram socket - ")+strerror(errno)));
	}

	return readSize;
}

void  DatagramSocket::setTimeout(unsigned long timeout)
{
	this->timeout.tv_usec = (timeout % 1000) * 1000;
	this->timeout.tv_sec = timeout / 1000;
}

unsigned long DatagramSocket::getTimeout()
{
	unsigned long timeout;

	timeout = this->timeout.tv_sec * 1000;
	timeout += this->timeout.tv_usec / 1000;

	return timeout;
}

void DatagramSocket::release()
{
	this->releaseForced=true;
#ifdef _WIN32
	shutdown(socketFD,SD_BOTH);
#else
	shutdown(socketFD,SHUT_RDWR);
#endif
}

bool DatagramSocket::check4readMsg(unsigned long timeout)
{
	boost::mutex::scoped_lock lock(mutRead);

	struct timeval time;

	time.tv_usec = (timeout % 1000) * 1000;
	time.tv_sec = timeout / 1000;

	fd_set fdList;
	FD_ZERO(&fdList);
	FD_SET(socketFD, &fdList);

	if(select(socketFD+1,&fdList,NULL,NULL,&time) <= 0)
		return false;
	else
		return true;
}

IPAddress DatagramSocket::getLocalAddress()
{
	return ipAddress;
}

IPAddress DatagramSocket::getPeerAddress()
{
	return peerAddress;
}

}
